# ！/usr/bin/env python
# _*_ coding utf-8 _*_
# python协程gevent模块
# https://www.cnblogs.com/cjaaron/p/9178083.html
from gevent.local import local
import gevent
from gevent.queue import Queue
from gevent.event import AsyncResult
from gevent.event import Event
from gevent import Timeout
import socket
from gevent import monkey


def test1():
    print(12)
    gevent.sleep(0)
    print(34)


def test2():
    print(56)
    gevent.sleep(0)
    print(78)


gevent.joinall([
    gevent.spawn(test1),
    gevent.spawn(test2)])

# 1、猴子补丁 Monkey patching：这个补丁是Gevent模块最需要注意的问题，有了它，才会让Gevent模块发挥它的作用。我们往往使用Gevent是为了实现网络通信的高并发，官网上还是建议使用”patch_all()”，而且在程序的第一行就执行。
monkey.patch_socket()

urls = ['www.baidu.com', 'www.gevent.org', 'www.python.org']
jobs = [gevent.spawn(socket.gethostbyname, url) for url in urls]
gevent.joinall(jobs, timeout=5)

print([job.value for job in jobs])
# 2、获取协程状态
# started属性/ready()方法：判断协程是否已启动。
# successful()方法：判断协程是否成功运行且没有抛出异常。
# value属性：获取协程执行完之后的返回值。
# 另外，greenlet协程运行过程中发生的异常是不会被抛出到协程外的，因此需要用协程对象的”exception”属性来获取协程中的异常。


def win():
    return 'You win!'


def fail():
    raise Exception('You failed!')


winner = gevent.spawn(win)
loser = gevent.spawn(fail)

print(winner.started)  # True
print(loser.started)   # True

# 在Greenlet中发生的异常，不会被抛到Greenlet外面。
# 控制台会打出Stacktrace，但程序不会停止
try:
    gevent.joinall([winner, loser])
except Exception as e:
    # 这段永远不会被执行
    print('This will never be reached')

print(winner.ready())  # True
print(loser.started)   # True

print(winner.value)  # 'You win!'
print(loser.value)   # None

print('successful ', winner.successful())  # True
print('successful ', loser.successful())   # False

# 这里可以通过raise loser.exception 或 loser.get()
# 来将协程中的异常抛出
print(loser.exception)

# 3、协程运行超时控制

timeout = Timeout(2)  # 2 seconds 将超时设为2秒，此后所有协程的运行，如果超过两秒就会抛出”Timeout”异常。
timeout.start()


def wait():
    gevent.sleep(10)


try:
    gevent.spawn(wait).join()
except Timeout:
    print('Could not complete')

# 4、事件（Event）对象
# greenlet协程间的异步通讯可以使用事件（Event）对象。该对象的”wait()”方法可以阻塞当前协程，而”set()”方法可以唤醒之前阻塞的协程。在下面的例子中，5个waiter协程都会等待事件evt，当setter协程在3秒后设置evt事件，所有的waiter协程即被唤醒。
evt = Event()


def setter():
    print('Wait for me')
    gevent.sleep(3)  # 3秒后唤醒所有在evt上等待的协程
    print("Ok, I'm done")
    evt.set()  # 唤醒


def waiter():
    print("I'll wait for you")
    evt.wait()  # 等待
    print('Finish waiting')


gevent.joinall([
    gevent.spawn(setter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
    gevent.spawn(waiter)
])

# 5、AsyncResult事件，它可以在唤醒时传递消息。让我们将上例中的setter和waiter作如下改动:
aevt = AsyncResult()


def setter():
    print('Wait for me')
    gevent.sleep(3)  # 3秒后唤醒所有在evt上等待的协程
    print("Ok, I'm done")
    aevt.set('Hello!')  # 唤醒，并传递消息


def waiter():
    print("I'll wait for you")
    message = aevt.get()  # 等待，并在唤醒时获取消息
    print('Got wake up message: %s' % message)


gevent.joinall([
    gevent.spawn(setter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
    gevent.spawn(waiter),
    gevent.spawn(waiter)
])
# 6、队列Queue，我们可以用它的put和get方法来存取队列中的元素。gevent的队列对象可以让greenlet协程之间安全的访问。运行下面的程序，你会看到3个消费者会分别消费队列中的产品，且消费过的产品不会被另一个消费者再取到：

products = Queue()


def consumer(name):
    # while not products.empty():
    while True:
        try:
            print('%s got product %s' % (name, products.get_nowait()))
            gevent.sleep(0)
        except gevent.queue.Empty:
            break

    print('Quit')


def producer():
    for i in range(1, 10):
        products.put(i)


gevent.joinall([
    gevent.spawn(producer),
    gevent.spawn(consumer, 'steve'),
    gevent.spawn(consumer, 'john'),
    gevent.spawn(consumer, 'nancy'),
])
# 注意：协程队列跟线程队列是一样的，put和get方法都是阻塞式的，它们都有非阻塞的版本：put_nowait和get_nowait。如果调用get方法时队列为空，则是不会抛出”gevent.queue.Empty”异常。我们只能使用get_nowait()的方式让其抛出异常。

# 7、协程本地变量，同线程类似，协程也有本地变量，也就是只在当前协程内可被访问的变量：
data = local()


def f1():
    data.x = 1
    print(data.x)


def f2():
    try:
        print(data.x)
    except AttributeError:
        print('x is not visible')


gevent.joinall([
    gevent.spawn(f1),
    gevent.spawn(f2)
])
